#include "common.h"
#include "memory/tlb.h"
#include "memory/cache.h"
#include "nemu.h"
#include "burst.h" 
//3022206130 3022206130 3022206130 3022206130 3022206130
//前置函数声明
uint32_t dram_read(hwaddr_t, size_t);
void dram_write(hwaddr_t, size_t, uint32_t);

/**
 * 段地址转换函数
 * 根据当前处理器状态将段地址转换为线性地址
 * 
 * @param addr 段内的偏移地址
 * @param len 要访问的数据长度，此参数在转换过程中未被使用
 * @param sreg_id 段寄存器ID，用于选择段的基地址
 * @return 返回转换后的线性地址
 */
lnaddr_t seg_translate(swaddr_t addr, size_t len, uint8_t sreg_id) {
  // 如果保护模式未启用，则无需进行地址转换，直接返回原始地址
  if (cpu.cr0.protect_enable == 0) return addr;
  else {
    // 否则，根据段寄存器的基地址和偏移地址进行转换
    return cpu.sreg[sreg_id].base + addr;
  }
}

/**
 * 页翻译函数，将逻辑地址转换为物理地址
 * 
 * 当CPU的控制寄存器CR0中的保护模式和分页模式都启用时，此函数执行页翻译。
 * 页翻译涉及通过页目录和页表将逻辑地址映射到物理地址。此过程包括：
 * 1. 使用逻辑地址的高位找到页目录中的项。
 * 2. 检查项是否存在（即项的 Present 位是否为 1）。
 * 3. 如果项存在，则使用该项找到页表。
 * 4. 在页表中找到具体的页，并再次检查页是否存在。
 * 5. 如果页存在，则计算出物理地址并返回。
 * 如果在翻译过程中发现页或页目录项不可用，则断言失败并停止执行。
 * 
 * @param addr 逻辑地址，需要翻译成物理地址
 * @return 翻译后的物理地址
 */
hwaddr_t page_translate(lnaddr_t addr) {
  // 检查CPU是否处于保护模式且启用了分页机制
  if (cpu.cr0.protect_enable == 1 && cpu.cr0.paging == 1) {
    // 从逻辑地址中提取页目录索引、页表索引和偏移量
    uint32_t dir = addr >> 22;
    uint32_t page = (addr >> 12) & 0x3ff;
    uint32_t bias = addr & 0xfff;

    // 查找TLB（Translation Lookaside Buffer）以加速地址翻译
    int i = read_tlb(addr);
    if (i != -1) return (tlb[i].page_num << 12) + bias;

    // 从CR3寄存器获取页目录的起始地址
    uint32_t dir_start = cpu.cr3.page_directory_base;
    // 计算页目录项的位置
    uint32_t dir_position = (dir_start << 12) + (dir << 2);
    // 读取页目录项的内容
    Page_Descriptor first_content;
    first_content.val = hwaddr_read(dir_position, 4);
    // 确保页目录项存在
    Assert(first_content.p == 1, "Page unavailable");
    // 获取页表的起始地址
    uint32_t page_start = first_content.addr;
    // 计算页表项的位置
    uint32_t page_pos = (page_start << 12) + (page << 2);
    // 读取页表项的内容
    Page_Descriptor second_content;
    second_content.val =  hwaddr_read(page_pos, 4);
    // 确保页表项存在
    Assert(second_content.p == 1, "Page unavailable");
    // 获取页的起始地址
    uint32_t addr_start = second_content.addr;
    // 计算物理地址
    hwaddr_t hwaddr = (addr_start << 12) + bias;
    // 将翻译结果写入TLB以加速后续翻译
    write_tlb(addr, hwaddr);
    // 返回物理地址
    return hwaddr;
  } else {
    // 如果未启用保护模式或分页模式，直接返回逻辑地址
    return addr;
  }
}


/* Memory accessing interfaces */

/**
 * 从硬件地址读取数据
 * 
 * 本函数通过访问缓存来读取指定硬件地址上的数据如果需要读取的数据跨越了缓存块的边界，
 * 则会涉及两个缓存行的读取和合并
 * 
 * @param addr 要读取的硬件地址
 * @param len 要读取的数据长度，以字节为单位
 * @return 返回读取到的数据，作为一个32位无符号整数返回
 */
uint32_t hwaddr_read(hwaddr_t addr, size_t len) {
  // 读取第一个缓存行的索引
  int cache_L1_way_1_index = read_cache_L1(addr);
  // 计算地址在缓存块内的偏移量
  uint32_t block_bias = addr & (CACHE_BLOCK_SIZE - 1);
  // 准备一个缓冲区来存储读取的数据，大小为两倍的BURST_LEN
  uint8_t ret[BURST_LEN << 1];
  //printf("%d\n", block_bias);
  // 如果需要读取的数据跨越了缓存块的边界
  if (block_bias + len > CACHE_BLOCK_SIZE) {
    // 读取第二个缓存行的索引
    int cache_L1_way_2_index = read_cache_L1(addr + CACHE_BLOCK_SIZE - block_bias);
    // 从第一个缓存行复制数据到缓冲区
    memcpy(ret, cache_L1[cache_L1_way_1_index].data + block_bias, CACHE_BLOCK_SIZE - block_bias);
    // 从第二个缓存行复制数据到缓冲区，完成跨越边界的数据合并
    memcpy(ret  + CACHE_BLOCK_SIZE - block_bias, cache_L1[cache_L1_way_2_index].data, len - (CACHE_BLOCK_SIZE - block_bias));
  } else {
    // 如果不跨越缓存块边界，直接从缓存行中复制数据到缓冲区
    memcpy(ret, cache_L1[cache_L1_way_1_index].data + block_bias, len);
  }
  // 初始化一个临时变量用于后续处理
  int tmp = 0;
  // 从缓冲区中读取非对齐的数据，并转换为32位无符号整数返回
  // 数据长度小于4字节时，通过掩码操作确保返回的数据位数与读取长度匹配
  uint32_t ans = unalign_rw(ret + tmp, 4) & (~0u >> ((4 - len) << 3));
  return ans;
}
/**
 * 向硬件地址写入数据。
 * 此函数封装了数据写入操作，以简化与硬件地址的交互。
 * 
 * @param addr 要写入的硬件地址。
 * @param len 要写入的数据长度。
 * @param data 要写入的数据。
 * 
 */
void hwaddr_write(hwaddr_t addr, size_t len, uint32_t data) {
  write_cache_L1(addr, len, data);/**
 * 从逻辑地址读取指定长度的数据。
 * 
 * 此函数旨在从给定的逻辑地址读取1、2或4个字节的数据，并返回读取到的值。
 * 它首先检查请求读取的长度是否符合预期，然后根据当前地址的偏移量和读取长度，
 * 决定是直接读取还是分两次读取（如果跨越了页面边界）。
 * 
 * @param addr 逻辑地址，从中读取数据。
 * @param len 要读取的字节数，只能是1、2或4。
 * @return 读取到的数据，作为一个uint32_t值返回。
 * 
 * 注意：此函数依赖于页面翻译和硬件地址读取机制。
 */
uint32_t lnaddr_read(lnaddr_t addr, size_t len) {
  // 确保读取长度是1、2或4字节。
  assert(len == 1 || len == 2 || len == 4);
  
  // 计算当前地址的偏移量。
  uint32_t cur_bias = addr & 0xfff;
  
  // 检查读取操作是否跨越了页面边界。
  if (cur_bias + len - 1 > 0xfff) {
    // 跨越页面边界的处理被注释掉，因为其依赖于具体机器实现。
    // 采用分两次读取的方式处理跨页面边界的情况。
    size_t len1 = 0xfff - cur_bias + 1;
    size_t len2 = len - len1;
    uint32_t addr_len1 = lnaddr_read(addr, len1);
    uint32_t addr_len2 = lnaddr_read(addr + len1, len2);
    // 合并两次读取的结果，并返回。
    uint32_t value = (addr_len2 << (len1 << 3)) | addr_len1;
    return value;

  } else {
    // 不跨越页面边界，直接进行页面翻译并读取数据。
    hwaddr_t hwaddr = page_translate(addr);
    return hwaddr_read(hwaddr, len);
  }
}
}

/**
 * 从逻辑地址读取指定长度的数据。
 * 
 * 此函数旨在从给定的逻辑地址读取1、2或4个字节的数据，并返回读取到的值。
 * 它首先检查请求读取的长度是否符合预期，然后根据当前地址的偏移量和读取长度，
 * 决定是直接读取还是分两次读取（如果跨越了页面边界）。
 * 
 * @param addr 逻辑地址，从中读取数据。
 * @param len 要读取的字节数，只能是1、2或4。
 * @return 读取到的数据，作为一个uint32_t值返回。
 * 
 * 注意：此函数依赖于页面翻译和硬件地址读取机制。
 */
uint32_t lnaddr_read(lnaddr_t addr, size_t len) {
  // 确保读取长度是1、2或4字节。
  assert(len == 1 || len == 2 || len == 4);
  
  // 计算当前地址的偏移量。
  uint32_t cur_bias = addr & 0xfff;
  
  // 检查读取操作是否跨越了页面边界。
  if (cur_bias + len - 1 > 0xfff) {
    // 跨越页面边界的处理被注释掉，因为其依赖于具体机器实现。
    // 采用分两次读取的方式处理跨页面边界的情况。
    size_t len1 = 0xfff - cur_bias + 1;
    size_t len2 = len - len1;
    uint32_t addr_len1 = lnaddr_read(addr, len1);
    uint32_t addr_len2 = lnaddr_read(addr + len1, len2);
    // 合并两次读取的结果，并返回。
    uint32_t value = (addr_len2 << (len1 << 3)) | addr_len1;
    return value;

  } else {
    // 不跨越页面边界，直接进行页面翻译并读取数据。
    hwaddr_t hwaddr = page_translate(addr);
    return hwaddr_read(hwaddr, len);
  }
}

/**
 * 向指定内存地址写入数据。
 *
 * 此函数执行向内存地址写入数据的操作，处理不同长度的数据。
 * 它确保写操作不会跨越内存页边界，如果需要会将写操作拆分为多个部分。
 *
 * @param addr 内存地址
 * @param len 数据长度（必须为1、2或4）
 * @param data 要写入的数据
 */
void lnaddr_write(lnaddr_t addr, size_t len, uint32_t data) {
  assert(len == 1 || len == 2 || len == 4);  // 检查数据长度是否有效

  uint32_t cur_bias = addr & 0xfff;  // 计算当前地址在页内的偏移量

  if (cur_bias + len - 1 > 0xfff) {
    // 如果写操作会跨越页边界
    size_t len1 = 0xfff - cur_bias + 1;  // 计算第一个部分的长度
    size_t len2 = len - len1;  // 计算第二个部分的长度

    lnaddr_write(addr, len1, data & ((1 << (len1 << 3)) - 1));  // 写入第一个部分
    lnaddr_write(addr + len1, len2, data >> (len1 << 3));  // 写入第二个部分
  } else {
    hwaddr_t hwaddr = page_translate(addr);  // 将逻辑地址转换为物理地址
    hwaddr_write(hwaddr, len, data);  // 执行实际的写操作
  }
}

/**
 * 从软件地址中读取指定长度的数据。
 * 
 * 此函数根据提供的软件地址和长度参数，转换地址并从线性地址空间中读取数据。
 * 在调试模式下，函数会断言长度参数只能是1、2或4，以确保数据读取操作的正确性。
 * 
 * @param addr 软件地址，用于指定读取数据的地址。
 * @param len 读取数据的长度，以字节为单位。只能是1、2或4。
 * @return 从线性地址空间读取的数据。
 */
uint32_t swaddr_read(swaddr_t addr, size_t len) {
    // 在调试模式下，确保读取的数据长度为1、2或4字节。
    #ifdef DEBUG
    assert(len == 1 || len == 2 || len == 4);
    #endif
    
    // 将软件地址转换为线性地址。
    lnaddr_t lnaddr = seg_translate(addr, len, current_sreg);
    
    // 从线性地址中读取数据并返回。
    return lnaddr_read(lnaddr, len);
}

/**
 * 向软件地址写入数据
 * 
 * 该函数根据给定的软件地址和数据长度，将数据写入到相应的线性地址中
 * 它首先通过地址翻译将软件地址转换为线性地址，然后进行实际的数据写入操作
 * 
 * @param addr 软件地址，表示要写入数据的目标位置
 * @param len 数据长度，表示要写入数据的大小，必须是1、2或4字节
 * @param data 要写入的数据，具体值取决于数据长度
 * 
 * 注：在调试模式下，如果数据长度不是1、2或4字节，程序将断言失败
 */
void swaddr_write(swaddr_t addr, size_t len, uint32_t data) {
#ifdef DEBUG
  // 在调试模式下，确保数据长度为1、2或4字节
  assert(len == 1 || len == 2 || len == 4);
#endif
  // 将软件地址翻译为线性地址
  lnaddr_t lnaddr = seg_translate(addr, len, current_sreg);
  // 向线性地址写入数据
  lnaddr_write(lnaddr, len, data);
}